#include "interop/ElfInterop.hpp"

#include <queue>

#include <elf.h>
#include <rewine/LoggerSingleton.hpp>

#ifdef __x86_64__
#  define ElfW(name) Elf64_##name
#  define ELFW(name) ELF64_##name
#else
#  define ElfW(name) Elf32_##name
#  define ELFW(name) ELF32_##name
#endif

namespace rewine
{

class entry_t {
public:
    const char *lpName;
    size_t szName;
    uint16_t ordinal;
    void *value;

public:
    entry_t(const char *lpName, size_t szName, uint16_t ordinal, void *value)
        : lpName(lpName), szName(szName), ordinal(ordinal), value(value) {}
};

typedef struct rw_elf_hash_t {
    uint32_t nbuckets;
    uint32_t symbias;
    uint32_t nbitmask; // must be a power of 2.
    uint32_t shift2;
    uint32_t data[0];
    // bitmask[__ELF_NATIVE_CLASS/32 * nbitmask]
    // buckets[nbuckets]
    // hashes[ndefines]
} hash_t;

void *
ElfInterop::rva2va(uintptr_t offset) const {
    return pImage->rva2va(offset);
}

static uint_fast32_t
djb_hash(const char *s) {
    uint32_t h = 5381;
    for (unsigned char c = *s; c != '\0'; c = *++s)
        h = ((h << 5) + h) + c;
    return h & 0xffffffff;
}

rw_elf_t
ElfInterop::buildELFInfo(const char *caller) {
    const char *reserved_strings[] = {
        "",
        "librewine.so",
        caller,
        nullptr
    };

    auto exportd = pImage->exportd;
    reserved_strings[2] = (const char *) rva2va(exportd->Name);

    size_t count = 0;
    size_t size = 0;
    std::queue<entry_t> q;
    auto iterNames = (PDWORD) rva2va(exportd->AddressOfNames);
    auto iterOrdinals = (PWORD) rva2va(exportd->AddressOfNameOrdinals);
    auto nbNames = exportd->NumberOfNames;
    for (int i = 0; i < nbNames; ++i, ++iterNames, ++iterOrdinals) {
        auto name = (const char *) rva2va(*iterNames);
        auto len = strlen(name);
        auto ordinal = *iterOrdinals;
        auto pExport = pImage->getExportByIndex(ordinal);
        auto value = pExport->getValue();
        q.push(entry_t(name, len, ordinal, value));
        count += 1;
        size += len + 1; // tailing-NULL
    }

    auto pReserved = reserved_strings;
    while (*pReserved) {
        auto sz0 = strlen(*pReserved) + 1;
        size += sz0;
        ++pReserved;
    }

    auto strtab = (char *) malloc(sizeof(char) * size);
    auto symtab = (ElfW(Sym) *) malloc(sizeof(ElfW(Sym)) * (1 + count));

    pReserved = reserved_strings;
    auto pstr = strtab;
    while (*pReserved) {
        auto sz0 = strlen(*pReserved) + 1;
        memcpy(pstr, pReserved, sz0);
        pstr += sz0;
        ++pReserved;
    }

    auto psym = symtab;
    // symtab starts with NULL symbol
    psym->st_name = 0;
    psym->st_value = (ElfW(Addr)) NULL;
    psym->st_size = 0;
    psym->st_info = ELFW(ST_INFO) (STB_LOCAL, STT_NOTYPE);
    psym->st_other = 0;
    psym->st_shndx = 0;
    ++psym;

    size_t offstr = 0;
    while (!q.empty()) {
        auto e = q.front(); q.pop();

        psym->st_name = offstr;
        psym->st_value = (ElfW(Addr)) e.value;
        psym->st_size = 0;
        psym->st_info = ELFW(ST_INFO) (STB_GLOBAL, STT_NOTYPE);
        psym->st_other = STV_DEFAULT;
        psym->st_shndx = 1; // anything but SHN_ABS
        ++psym;

        // every string in strtab is tailing with NULL
        auto sz0 = e.szName + 1;
        memcpy(pstr, e.lpName, sz0);
        pstr += sz0;
        offstr += sz0;
    }

    auto nundefs = 1; // 1 for symtab[0]
    auto ndefs = count;
    auto nbuckets = 1;
    auto cbitmask = sizeof(void *) / sizeof(int);
    auto nbitmask = 2; // must be 2^n
    auto hash = (hash_t *) malloc(sizeof(hash_t) + sizeof(uint32_t) * ((cbitmask * nbitmask) + nbuckets + ndefs));
    hash->nbuckets = nbuckets;
    hash->symbias = nundefs;
    hash->nbitmask = nbitmask;
    hash->shift2 = 1;

    auto phash = &hash->data[0];
    // filters
    for (int i = 0; i < cbitmask * nbitmask; ++i) {
        *phash++ = -1; // 100% pass
    }
    // buckets
    *phash++ = nundefs; // start at symtab[symbias]

    // hashes
    --psym;
    phash += ndefs-1;
    for (int i = ndefs; i; --i, --psym, --phash) {
        const char *name = (char *) strtab + psym->st_name;
        uint_fast32_t hval = djb_hash(name) & ~1;
        uint32_t lsb = (i == ndefs) ? 1 : 0; // || (hash != (*(phash+1) & ~1)) ? 1 : 0;
        *phash = hval | lsb;
    }

    elf.count = count;
    elf.strtab = strtab;
    elf.strsz = size;
    elf.symtab = symtab;
    elf.hash = hash;

    elf.needed = elf.strtab + 1;
    elf.soname = elf.needed + strlen(reserved_strings[1]) + 1;

    return elf;
}

void ElfInterop::updateSymtab(uint32_t symcnt, void *symtab0, void *strtab0) {
    auto symtab = (ElfW(Sym) *) symtab0;
    auto psym = symtab;
    ++psym;

    auto strtab = (const char *) strtab0;
    for (int i = 0; i < symcnt; ++i) {
        auto name = strtab + psym->st_name;
        auto pExport = pImage->getExportByName(name, -1);
        g_Logger->debug(__FLF__, "update {} to {}", name, pExport->getValue());
        psym->st_value = (ElfW(Addr)) pExport->getValue();
        psym->st_size = 0;
        psym->st_info = ELFW(ST_INFO) (STB_GLOBAL, STT_NOTYPE);
        psym->st_other = STV_DEFAULT;
        psym->st_shndx = 1; // anything but SHN_ABS
        ++psym;
    }

    /*
    auto exportd = pImage->exportd;
    auto nbNames = exportd->NumberOfNames;

    size_t count = 0;
    size_t size = 0;
    std::queue<entry_t> q;
    auto iterNames = (PDWORD) rva2va(exportd->AddressOfNames);
    auto iterOrdinals = (PWORD) rva2va(exportd->AddressOfNameOrdinals);
    auto tblAddress = (PDWORD) rva2va(exportd->AddressOfFunctions);
    for (int i = 0; i < nbNames; ++i, ++iterNames, ++iterOrdinals, ++psym) {
        auto name = (const char *) rva2va(*iterNames);
        auto len = strlen(name);
        auto ordinal = *iterOrdinals;
        auto value = tblAddress[ordinal];

        psym->st_value = (ElfW(Addr)) rva2va(value);
        psym->st_size = 0;
        psym->st_info = ELFW(ST_INFO) (STB_GLOBAL, STT_NOTYPE);
        psym->st_other = STV_DEFAULT;
        psym->st_shndx = 1; // anything but SHN_ABS

        auto va = (intptr_t *) rva2va(value);
        fprintf(stdout, "%s = %p %lx\n", name, va, *va);

        count += 1;
        size += len + 1; // tailing-NULL
    }
     */
}

} // namespace rewine
